"""
回滚服务
"""

import asyncio
import shutil
from pathlib import Path
from typing import  List, Dict, Any, Optional
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, desc

from models.execution import Execution
from models.repository import Repository
from models.task import Task
from models.log import Log
from services.git_service import git_service
from utils.logger import get_logger

logger = get_logger(__name__)


class RollbackService:
    """回滚服务"""

    def __init__(self):
        self.git_service = git_service

    async def rollback_execution(
        self,
        execution_id: int,
        db: AsyncSession
    ) -> Dict[str, Any]:
        """回滚执行"""
        try:
            # 获取执行记录
            execution = await db.get(Execution, execution_id)
            if not execution:
                raise ValueError(f"执行记录不存在: {execution_id}")

            # 检查是否已经回滚
            if execution.rollback_execution_id:
                return {
                    "execution_id": execution_id,
                    "status": "already_rolled_back",
                    "rollback_execution_id": execution.rollback_execution_id,
                    "message": "该执行记录已经回滚过了"
                }

            # 获取任务信息
            task = await db.get(Task, execution.task_id)
            if not task:
                raise ValueError(f"任务不存在: {execution.task_id}")

            logger.info(
                "开始回滚执行",
                execution_id=execution_id,
                task_id=task.id,
                task_name=task.name
            )

            # 创建回滚执行记录
            rollback_execution = Execution(
                task_id=task.id,
                status="pending",
                triggered_by="manual",
                branch_mapping=execution.branch_mapping,
                rollback_execution_id=execution_id
            )

            db.add(rollback_execution)
            await db.commit()
            await db.refresh(rollback_execution)

            # 执行回滚操作
            rollback_result = await self._execute_rollback(
                execution=execution,
                rollback_execution=rollback_execution,
                task=task,
                db=db
            )

            # 更新执行记录
            execution.rollback_execution_id = rollback_execution.id
            await db.commit()

            logger.info(
                "执行回滚完成",
                execution_id=execution_id,
                rollback_execution_id=rollback_execution.id,
                status=rollback_result["status"]
            )

            return rollback_result

        except Exception as e:
            logger.error(
                "执行回滚失败",
                execution_id=execution_id,
                error=str(e)
            )
            raise

    async def _execute_rollback(
        self,
        execution: Execution,
        rollback_execution: Execution,
        task: Task,
        db: AsyncSession
    ) -> Dict[str, Any]:
        """执行回滚操作"""
        try:
            # 更新回滚执行状态
            rollback_execution.status = "running"
            rollback_execution.start_time = execution.start_time
            await db.commit()

            await self._log_rollback_step(
                rollback_execution.id,
                "info",
                "开始执行回滚操作"
            )

            rollback_results = []

            # 回滚每个目标仓库
            for target_result in execution.target_results:
                if target_result.get("status") != "success":
                    # 跳过失败的仓库
                    continue

                repo_id = target_result.get("repo_id")
                if not repo_id:
                    continue

                repo_result = await self._rollback_repository(
                    repo_id=repo_id,
                    execution=execution,
                    rollback_execution=rollback_execution,
                    task=task,
                    db=db
                )

                rollback_results.append(repo_result)

            # 计算回滚结果
            success_count = len([r for r in rollback_results if r.get("status") == "success"])
            failure_count = len(rollback_results) - success_count

            # 更新回滚执行状态
            end_time = execution.end_time
            rollback_execution.end_time = end_time
            rollback_execution.duration = execution.duration
            rollback_execution.status = "success" if failure_count == 0 else "partial_failure"
            rollback_execution.target_results = rollback_results

            if failure_count > 0:
                rollback_execution.error_message = f"部分仓库回滚失败 ({failure_count}/{len(rollback_results)})"

            await db.commit()

            await self._log_rollback_step(
                rollback_execution.id,
                "info" if failure_count == 0 else "warning",
                f"回滚操作完成 (成功: {success_count}, 失败: {failure_count})"
            )

            return {
                "execution_id": execution.id,
                "rollback_execution_id": rollback_execution.id,
                "status": rollback_execution.status,
                "success_count": success_count,
                "failure_count": failure_count,
                "total_count": len(rollback_results),
                "rollback_results": rollback_results
            }

        except Exception as e:
            # 更新回滚执行状态为失败
            rollback_execution.status = "failure"
            rollback_execution.error_message = str(e)
            rollback_execution.end_time = execution.end_time
            if rollback_execution.start_time:
                rollback_execution.duration = int(
                    (rollback_execution.end_time - rollback_execution.start_time).total_seconds()
                )

            await db.commit()

            await self._log_rollback_step(
                rollback_execution.id,
                "error",
                f"回滚操作失败: {str(e)}"
            )

            raise

    async def _rollback_repository(
        self,
        repo_id: int,
        execution: Execution,
        rollback_execution: Execution,
        task: Task,
        db: AsyncSession
    ) -> Dict[str, Any]:
        """回滚单个仓库"""
        try:
            # 获取目标仓库信息
            target_repo = await db.get(Repository, repo_id)
            if not target_repo:
                return {
                    "repo_id": repo_id,
                    "repo_name": "未知仓库",
                    "status": "error",
                    "message": "仓库不存在"
                }

            # 获取目标分支
            target_branch = task.get_branch_mapping(repo_id)
            if not target_branch:
                target_branch = execution.branch_mapping.get("source_branch", "main")

            await self._log_rollback_step(
                rollback_execution.id,
                "info",
                f"开始回滚仓库: {target_repo.name} (分支: {target_branch})",
                repo_id=repo_id
            )

            # 尝试回滚到上一个成功的提交
            rollback_result = await self._rollback_to_previous_commit(
                target_repo=target_repo,
                target_branch=target_branch,
                execution=execution,
                db=db
            )

            await self._log_rollback_step(
                rollback_execution.id,
                "info" if rollback_result["status"] == "success" else "error",
                f"仓库回滚完成: {rollback_result['message']}",
                repo_id=repo_id,
                details=rollback_result.get("details")
            )

            return rollback_result

        except Exception as e:
            logger.error(
                "仓库回滚失败",
                repo_id=repo_id,
                error=str(e)
            )
            return {
                "repo_id": repo_id,
                "repo_name": "未知仓库",
                "status": "error",
                "message": f"回滚失败: {str(e)}"
            }

    async def _rollback_to_previous_commit(
        self,
        target_repo: Repository,
        target_branch: str,
        execution: Execution,
        db: AsyncSession
    ) -> Dict[str, Any]:
        """回滚到上一个成功的提交"""
        try:
            # 查找上一个成功的执行记录
            stmt = select(Execution).where(
                Execution.task_id == execution.task_id,
                Execution.status == "success",
                Execution.id < execution.id,
                Execution.rollback_execution_id.is_(None)
            ).order_by(desc(Execution.created_at)).limit(1)

            result = await db.execute(stmt)
            previous_execution = result.scalar_one_or_none()

            if not previous_execution:
                return {
                    "status": "warning",
                    "message": "没有找到上一个成功的执行记录，无法回滚",
                    "details": {
                        "execution_id": execution.id,
                        "current_commit": execution.source_commit
                    }
                }

            # 获取上一个成功的提交ID
            previous_commit = previous_execution.source_commit
            if not previous_commit:
                return {
                    "status": "error",
                    "message": "上一个执行记录没有提交ID",
                    "details": {
                        "previous_execution_id": previous_execution.id,
                        "execution_id": execution.id
                    }
                }

            # 克隆目标仓库
            repo_cache_dir = self.git_service._get_cache_dir(f"rollback_{target_repo.id}")
            await self.git_service.clone_repository(target_repo, repo_cache_dir)

            # 切换到目标分支
            await self.git_service.get_commit_id(repo_cache_dir, target_branch)

            # 重置到上一个提交
            repo_cache_dir.mkdir(parents=True, exist_ok=True)
            import subprocess

            try:
                # 使用git reset --hard回滚到指定提交
                subprocess.run(
                    ["git", "reset", "--hard", previous_commit],
                    cwd=repo_cache_dir,
                    check=True,
                    capture_output=True,
                    text=True
                )

                # 如果目标分支存在，强制推送
                await self.git_service.push_to_target(
                    repo_cache_dir,
                    target_repo,
                    target_branch,
                    force_push=True
                )

                current_commit = await self.git_service.get_commit_id(repo_cache_dir, target_branch)

                return {
                    "status": "success",
                    "message": f"已回滚到提交 {previous_commit[:8]}",
                    "details": {
                        "previous_commit": previous_commit,
                        "current_commit": current_commit,
                        "previous_execution_id": previous_execution.id,
                        "rollback_execution_id": execution.id
                    }
                }

            except subprocess.CalledProcessError as e:
                return {
                    "status": "error",
                    "message": f"Git操作失败: {e.stderr}",
                    "details": {
                        "previous_commit": previous_commit,
                        "error": e.stderr
                    }
                }

        except Exception as e:
            logger.error(
                "回滚到上一个提交失败",
                repo_id=target_repo.id,
                target_branch=target_branch,
                error=str(e)
            )
            return {
                "status": "error",
                "message": f"回滚失败: {str(e)}",
                "details": {
                    "target_repo_id": target_repo.id,
                    "target_branch": target_branch,
                    "previous_commit": previous_commit if 'previous_execution' in locals() else None
                }
            }

    async def _log_rollback_step(
        self,
        rollback_execution_id: int,
        level: str,
        message: str,
        repo_id: Optional[int] = None,
        details: Optional[Dict[str, Any]] = None
    ) -> None:
        """记录回滚步骤日志"""
        async with get_db_session() as db:
            log_entry = Log(
                execution_id=rollback_execution_id,
                level=level,
                message=message,
                step="rollback",
                repo_id=repo_id,
                details=details
            )
            db.add(log_entry)
            await db.commit()

    async def get_rollback_history(
        self,
        execution_id: int,
        db: AsyncSession
    ) -> List[Dict[str, Any]]:
        """获取回滚历史"""
        try:
            # 查询所有相关的回滚记录
            stmt = select(Execution).where(
                Execution.rollback_execution_id == execution_id
            ).order_by(Execution.created_at.desc())

            result = await db.execute(stmt)
            rollback_executions = result.scalars().all()

            rollback_history = []
            for rollback_exec in rollback_executions:
                rollback_history.append({
                    "rollback_execution_id": rollback_exec.id,
                    "status": rollback_exec.status,
                    "start_time": rollback_exec.start_time.isoformat() if rollback_exec.start_time else None,
                    "end_time": rollback_exec.end_time.isoformat() if rollback_exec.end_time else None,
                    "duration": rollback_exec.duration,
                    "success_count": len([r for r in (rollback_exec.target_results or []) if r.get("status") == "success"]),
                    "failure_count": len([r for r in (rollback_exec.target_results or []) if r.get("status") == "failure"]),
                    "error_message": rollback_exec.error_message
                })

            return rollback_history

        except Exception as e:
            logger.error(
                "获取回滚历史失败",
                execution_id=execution_id,
                error=str(e)
            )
            return []

    async def can_rollback(self, execution_id: int, db: AsyncSession) -> Dict[str, Any]:
        """检查是否可以回滚"""
        try:
            # 获取执行记录
            execution = await db.get(Execution, execution_id)
            if not execution:
                return {
                    "can_rollback": False,
                    "reason": "执行记录不存在"
                }

            # 检查是否已经回滚过
            if execution.rollback_execution_id:
                return {
                    "can_rollback": False,
                    "reason": "该执行已经回滚过了"
                }

            # 检查执行状态
            if execution.status not in ["success", "partial_failure"]:
                return {
                    "can_rollback": False,
                    "reason": f"执行状态为 {execution.status}，无法回滚"
                }

            # 检查是否有成功的推送
            success_count = len([r for r in execution.target_results if r.get("status") == "success"])
            if success_count == 0:
                return {
                    "can_rollback": False,
                    "reason": "没有成功的推送，无需回滚"
                }

            return {
                "can_rollback": True,
                "reason": "可以执行回滚操作",
                "success_count": success_count,
                "total_count": len(execution.target_results)
            }

        except Exception as e:
            logger.error(
                "检查回滚条件失败",
                execution_id=execution_id,
                error=str(e)
            )
            return {
                "can_rollback": False,
                "reason": f"检查失败: {str(e)}"
            }


# 全局回滚服务实例
rollback_service = RollbackService()


# 数据库会话获取函数
async def get_db_session() -> AsyncSession:
    """获取数据库会话"""
    from database import AsyncSessionLocal
    return AsyncSessionLocal()